import xlrd,openpyxl,struct,os,threading,timeit,math,multiprocessing,re
from xlrd.sheet import XL_CELL_EMPTY
from SheetInfo import SheetInfo 
from utils import FileUtil,PBGenUtil,SettingUtil

TABLE_CONFIG_MAP = {}


def readSheetInfos(excels:list)->list:
    sheetInfos = []
    totalTheads = []
    count = len(excels)
    for i in range(count):
        f = excels[i]
        # sheetInfos.extend(excelToSheetInfos(f))
        t = threading.Thread(target=_readSheetWorker,args=[f,sheetInfos])
        # t = multiprocessing.Process(target=_readSheetWorker,args=[f,sheetInfos])
        t.start()
        totalTheads.append(t)
        print("开始读取 f=",f)
    
    idx = 1
    for t in totalTheads:
        t.join()
        print(f"等待完成 {idx} {len(totalTheads)}")
        idx +=1
    print(f"总的表格数={len(sheetInfos)}")
    return sheetInfos

def _readSheetWorker(f,sheetInfos):
    sheetInfos.extend(excelToSheetInfos(f))

def excelToSheetInfos(fileName:str)->list:
    """把单个excel文件的所有sheet表转换成SheetInfo数组

    Args:
        fileName (str): 文件夹路径

    Returns:
        list: 返回 [SheetInfo,...]
    """
    st = timeit.default_timer()
    sheets = read(fileName)
    et = timeit.default_timer()
    costMS = math.floor((et-st)*1000)
    print(f'+++++++++--读取{fileName}~~~耗时{costMS/1000}秒')
    
    sheetInfoArr = []
    for sheet in sheets:
        # sheetName = sheet.title
        sheetName = sheet.name
        if not sheetName.startswith("t_"): #以 t_ 开头的表才读取
            continue
        st = timeit.default_timer()
        sheetRowDict = readSheet(sheet)
        et = timeit.default_timer()
        costMS = math.floor((et-st)*1000)
        print(f'~~导出{sheetName}~~~耗时{costMS/1000}秒')
        rowCount = len(sheetRowDict)
        if rowCount==0:#空表
            print(f"!!!! 表格[{fileName}],[{sheetName}]是空表")
            continue
        if rowCount<3:
            print(f"!!!! 表格[{fileName}],[{sheetName}]不是有效的配置表")
            continue
        info = SheetInfo(fileName,sheetName,sheetRowDict)
        try:
            if verifyTable(info.name):
                #info.writeProtoFile()
                sheetInfoArr.append(info)
        except Exception as e:
            print(f"!!!! 验证失败！异常={e},已跳过[{fileName}]的[{sheet.name}]")
    
    return sheetInfoArr

def filterConfig(sheetInfoArr):
    changeSheetInfos = []
    newTables = []
    configPbFile =  SettingUtil.getSetting().configPbFile
    usePb = SettingUtil.getSetting().usePB
    
    protoContent = None
    message = ""
    if usePb:
        if os.path.exists(configPbFile):
            protoContent = FileUtil.readTextFile(configPbFile)
        else:
            protoContent = FileUtil.toProtoContent(message)
    
    for sheetInfo in sheetInfoArr:
        newTables.append(sheetInfo.name+"="+sheetInfo.fileName)
        changeSheetInfos.append(sheetInfo)
        
        if usePb:
            message = sheetInfo.toMessageStruct()
            p = re.compile(r"message\s*?"+sheetInfo.name+r".*?{.*?}",re.DOTALL)
            msgStructs = re.findall(p,protoContent)
            if msgStructs !=None and len(msgStructs) == 0:
                protoContent += message
                print("-----新增 message ..",sheetInfo.name)
            else:
                message = str(message).rstrip()
                protoContent = protoContent.replace(msgStructs[0],message)
                print("-----替换 message ..",sheetInfo.name)
        
    newTableSet = set(newTables)
    return changeSheetInfos,newTableSet,protoContent

def removeMessage(protoContent,delSet):
    for item in delSet:
        p = re.compile(r"message\s*?"+item+r".*?{.*?}",re.DOTALL)
        msgStructs = re.findall(p,protoContent)
        if msgStructs !=None and len(msgStructs) > 0:
            protoContent = protoContent.replace(msgStructs[0],"")
            print("-----删除 message ..",item)
    return protoContent
            

def calChangeSet(oldChangeSets,newTabeSetToFind,newTableSetToWrite):
    oldTableMap = FileUtil.readConfigTableMap()
    delSet = oldChangeSets - newTabeSetToFind
    addSet = newTabeSetToFind - oldChangeSets #通过差集 查找新增的配置
    for url in oldTableMap.keys():
        tabs = oldTableMap[url]
        if os.path.exists(url):
            for tab in tabs:
                if tab not in delSet:
                    newTableSetToWrite.add(tab+"="+url)
        else:
            delSet = delSet.union(set(tabs)) 
    return delSet,addSet

#把修改过的表格逐个写入到pb文件
def writeSheetToPB(changeSheetInfos):
    sheetPbRoot = SettingUtil.getSetting().sheetPbRoot
    extName = SettingUtil.getSetting().dataExt
    for sheetInfo in changeSheetInfos:
        st = timeit.default_timer()
        sheetInfoBytes = bytearray()
        nameBytes = sheetInfo.name.encode()
        size = len(nameBytes)
        sheetInfoBytes += struct.pack("<H",size) #写入表格名字的长度 2个字节
        sheetInfoBytes += nameBytes #写入表格名字
        sheetPBName = sheetInfo.name+extName
        sheetBytes = sheetInfo.toByteArray()
        et = timeit.default_timer()
        # print(f"+++转换成byes={sheetPBName}总耗时 {et-st} ")
        buffSize = len(sheetBytes)
        #print(sheetInfo.name,"名字长度=",size,"表格数据长度=",buffSize)
        sheetInfoBytes += struct.pack("<i",buffSize)   #写入单个sheet表格数据长度
        sheetInfoBytes += sheetBytes
        FileUtil.writePBFile(os.path.join(sheetPbRoot,sheetPBName),sheetInfoBytes)
        et = timeit.default_timer()
        print(f"+++写入{sheetPBName}总耗时 {et-st} ")


def read(fileName:str)->list:
    try:
        doc = xlrd.open_workbook(fileName)
        # doc = openpyxl.load_workbook(fileName,read_only=True,data_only=True) #设置read_only data_only速度可以提升N倍
    except Exception as e:
        print('An exception occurred e=',e)
    sheets = doc.sheets()
    # sheets = doc.worksheets
    return sheets

def readSheet(sheet)->dict:
    sheetRowDict = {}
    
    if sheet.nrows < 4:
        return sheetRowDict

    for i in range(sheet.nrows):
        rowData = sheet.row(i)
        isEmpty = True #是否整行都是空的
        tempRows = []
        for cell in rowData:
            if cell.value !=None and cell.ctype !=XL_CELL_EMPTY:
                isEmpty = False
            tempRows.append(cell.value)
        if not isEmpty:
            sheetRowDict[i] = tempRows

    return sheetRowDict

def verifyTable(configName)->bool:
    flag = configName not in TABLE_CONFIG_MAP
    if not flag:
        raise Exception(f"存在重复的配置名称[{configName}]")
    else:
        TABLE_CONFIG_MAP[configName] = True
    return flag